﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.IO;

using SMBL.ConfigElement.FileLocate;
using SMBL.Global;

namespace SMBL
{
    namespace Core
    {
        public class LogSystem
        {
            //////////////////////////////////////////////////////////////////////////
            // Private Parameters
            //////////////////////////////////////////////////////////////////////////
            // Used for a single instance
            // the date time to write the log message
            private DateTime loggedDate;
            // The log Message
            private String loggedMessage;
            // The user which cause the log
            private String loggedUser;
            // The Id of the log
            private Int64 logId;

            //////////////////////////////////////////////////////////////////////////
            // Static Parameters
            // For the System Usage
            //////////////////////////////////////////////////////////////////////////
            // For Synchronization
            private static Boolean SynPool;
            private static Boolean SynFile;

            // Error Pool
            private static ArrayList LogPool;
            // System Information
            //private static Int32 gMaxCacheSize;
            private static Int32 gCacheSize;
            //private static Int32 gMaxFileSize;
            private static FileLocation gLogFileLocation;

            // Increased Error Id
            private static Int64 gLastLogId;

            /// <summary>
            /// Create an log Unit via the message sent in
            /// @Author: Push
            /// </summary>
            /// <param name="LogMessage"></param>
            /// <param name="LogUser"></param>
            private LogSystem(String LogMessage, String LogUser)
            {
                loggedDate = DateTime.Now;
                loggedMessage = LogMessage;
                loggedUser = LogUser;
                logId = ++gLastLogId;
            }

            //////////////////////////////////////////////////////////////////////////
            // System API
            //////////////////////////////////////////////////////////////////////////

            /// <summary>
            /// Initialize the log system
            /// 1. Check the log file
            /// 2. Initialize the log pool
            /// 3. Initialize Global Parameters
            /// @Author: Push
            /// </summary>
            /// <param name="LogFileLocation"></param>
            public static void Initialize(FileLocation LogFileLocation)
            {
                // Check Error File
                gLogFileLocation = LogFileLocation;
                FileInfo logFileInfo = new FileInfo(gLogFileLocation.Path);
                if (!logFileInfo.Exists)
                {
                    try
                    {
                        StreamWriter logFileStream = new StreamWriter(logFileInfo.Create());
                        logFileStream.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
                        logFileStream.WriteLine("<!DOCTYPE Log SYSTEM 'Log.dtd'>");
                        logFileStream.WriteLine("<Error></Error>");
                        logFileStream.Close();
                    }
                    catch (System.Exception e)
                    {
                        // Catch the UnHandled exception
                        BasicErrorSystem.UnhandledError(e);
                        // failed to initialize the error system
                        return;
                    }
                }

                // Check file size
                if (logFileInfo.Length / 1024 > gLogFileLocation.MaxSize)
                    CleanFile();
                // Initialize the Error Pool
                LogPool = new ArrayList();
                gCacheSize = 0;
                //gMaxCacheSize = ErrorLocate.CacheSize;
                gLastLogId = 0;
                SynPool = false;
                SynFile = false;
            }

            /// <summary>
            /// Write all logs to the file
            /// Release the resources
            /// @Author: Push
            /// </summary>
            public static void Terminate()
            {
                try
                {
                    // Write error
                    StreamToFile();
                }
                catch (Exception e)
                {
                    // UnHandled Exception
                    BasicErrorSystem.UnhandledError(e);
                }
            }

            /// <summary>
            /// Clean Log File
            /// Back up the old one, then create a new one
            /// </summary>
            private static void CleanFile()
            {
                // Lock
                while (SynFile) ;
                SynFile = true;

                // Backup old file
                //FileLocate ErrorLocate = FileLocate.GetLocate("Error");
                FileInfo logFileInfo = new FileInfo(gLogFileLocation.Path);
                try
                {
                    // Delete Old One
                    logFileInfo.MoveTo("Error." + DateTime.Now.ToString() + ".backup");
                    logFileInfo.Delete();

                    // Create new file
                    //errorFile.Create();
                    StreamWriter streamLogFile = new StreamWriter(logFileInfo.Create());
                    streamLogFile.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
                    streamLogFile.WriteLine("<!DOCTYPE Log SYSTEM 'Log.dtd'>");
                    streamLogFile.WriteLine("<Error></Error>");
                    streamLogFile.Close();
                }
                catch (System.Exception e)
                {
                    // IO Exception
                    BasicErrorSystem.UnhandledError(e);
                }

                // UnLock
                SynFile = false;
            }

            /// <summary>
            /// Get a log and add to the log pool
            /// @Author: Push
            /// </summary>
            /// <param name="LogMessage"></param>
            /// <param name="LogUser"></param>
            /// <returns>The log Id of the message</returns>
            public static long GetLog(String LogMessage, String LogUser)
            {
                // Check available
                while (SynPool) ;
                // New error
                LogSystem log = new LogSystem(LogMessage, LogUser);
                long LID = log.logId;
                LogPool.Add(log);

                gCacheSize += log.ToString().Length;
                // Check the Pool size
                CheckPoolSize();
                return LID;
            }

            /// <summary>
            /// Check current log pool's size
            /// Write all logs to file or send report mail
            /// @Author: Push
            /// </summary>
            private static void CheckPoolSize()
            {
                // Check the Pool Size
                if (gCacheSize / 1024 >= gLogFileLocation.MaxCacheSize)
                {
                    try
                    {
                        // Write the pool to the log file
                        StreamToFile();
                    }
                    catch (Exception exp)
                    {
                        // UnHandled IO Exception
                        BasicErrorSystem.UnhandledError(exp);
                        // Send current log pool
                        BasicErrorSystem.UnhandledError(LogPool);
                    }
                    // Clean the Pool;
                    LogPool.Clear();
                }
            }

            /// <summary>
            /// Write log pool to the log file
            /// The method does not need to catch the exceptions
            /// because all method invoke this one will catch them.
            /// @Author: Push
            /// </summary>
            private static void StreamToFile()
            {
                // Lock
                while (SynFile) ;
                SynFile = true;
                SynPool = true;

                System.Xml.XmlDocument LogXml = new System.Xml.XmlDocument();
                //ErrorXml.Load(ErrorLocate.Path);
                LogXml.Load(gLogFileLocation.Path);
                for (int i = LogPool.Count - 1; i >= 0; --i)
                {
                    // Get the error and generate the xml elements
                    LogSystem tmpLog = (LogSystem)LogPool[i];
                    System.Xml.XmlElement LogItem = LogXml.CreateElement("LogItem");
                    LogItem.SetAttribute("LID", tmpLog.logId.ToString());

                    System.Xml.XmlElement Date = LogXml.CreateElement("Date");
                    Date.InnerText = tmpLog.loggedDate.ToString();

                    System.Xml.XmlElement Message = LogXml.CreateElement("Message");
                    Message.InnerText = tmpLog.loggedMessage;

                    System.Xml.XmlElement User = LogXml.CreateElement("User");
                    User.InnerText = tmpLog.loggedUser;

                    // combine the exception
                    LogItem.AppendChild(Date);
                    LogItem.AppendChild(Message);
                    LogItem.AppendChild(User);

                    // Add to the file
                    LogXml.DocumentElement.AppendChild(LogItem);
                }

                // Save the XML file
                LogXml.Save(gLogFileLocation.Path);

                // Unlock
                SynPool = false;
                SynFile = false;

                // Check File size
                FileInfo logFileInfo = new FileInfo(gLogFileLocation.Path);
                if (logFileInfo.Length / 1024 >= gLogFileLocation.MaxSize)
                    CleanFile();
            }

            /// <summary>
            /// Get the logged message
            /// @Author: Push
            /// </summary>
            /// <param name="LID">The Id of the log to query</param>
            /// <returns>The logged Message</returns>
            public static String GetMessage(long LID)
            {
                // Check the log id range
                if (LID < 0) return "Log ID Out Of Range, no error message returned.";
                // Check if still in the Pool
                Int32 delta = (Int32)(LID - ((LogSystem)LogPool[0]).logId);
                if (delta >= 0)
                    return ((LogSystem)LogPool[delta]).loggedMessage;

                // do not search the file cause the log happened long time ago
                // the user should open the log page to see the detail of the error
                return "Too much time passed after the log catched. Open the log file to see the details";
            }

            //////////////////////////////////////////////////////////////////////////
            // Method
            //////////////////////////////////////////////////////////////////////////

            /// <summary>
            /// To String
            /// Used by the ErrorReport
            /// @Author: Push
            /// </summary>
            /// <returns>The Log String</returns>
            public override string ToString()
            {
                //return base.ToString();
                return this.loggedDate.ToString() + ": " +
                    this.loggedMessage + "<br />" +
                    this.loggedUser + "<br />";
            }
        }
    }
}